package com.ymx.darling.executor.mapping.relation;

import com.ymx.darling.bean.entity.BeanInfo;
import com.ymx.darling.bean.entity.FieldInfo;
import com.ymx.darling.bean.sql.BoundSql;
import com.ymx.darling.bean.sql.SqlCommandType;
import com.ymx.darling.bean.sql.Statement;
import com.ymx.darling.dialect.keygenerator.NoneKeyGenerator;
import com.ymx.darling.exception.EntityException;
import com.ymx.darling.executor.mapping.relation.annotation.RelationManyToMany;
import com.ymx.darling.session.setting.relation.RelationEntity;
import com.ymx.darling.util.ReflectUtil;
import com.ymx.darling.util.StringUtil;
import com.ymx.darling.wrapper.QueryWrapper;
import com.ymx.darling.wrapper.build.SelectBuilder;
import com.ymx.darling.bean.sql.SqlStatement;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 字段查询多对多关联加载器
 * @author 爱Java的小于
 */
public class RelationManyToManyLoader extends RelationLoader{
    private final RelationManyToMany manyToMany;

    public RelationManyToManyLoader(RelationManyToMany manyToMany) {
       this.manyToMany = manyToMany;
    }

    @Override
    public void invoke(Statement statement, RelationEntity relationEntity, List<?> resultList) {
        // 获取中间表中所有的数据
        Map<Object, List<Object>> joinTableResult = this.queryJoinTable(statement, resultList);
        if (!joinTableResult.isEmpty()) {
            BeanInfo targetBeanInfo = relationEntity.getBeanInfo();
            Map<String, Object> queryParam = statement.getNestedQueryParam();
            Set<Object> targetColumnValueKeySet = joinTableResult.keySet();
            // 构建目标表的sql
            SqlStatement sqlStatement = this.buildTargetQuerySql(targetBeanInfo, queryParam, targetColumnValueKeySet);
            Statement targetTableStatement = this.createStatement(statement, relationEntity, sqlStatement);
            // 执行目标表sql
            List<Object> targetResultList = statement.getExecutor().queryList(targetTableStatement);
            Map<Object, Object> targetResultMap = this.listToMap(targetResultList, this.getTargetFieldInfo(targetBeanInfo));
            // 将目标表数据组装到主查询实例的关联字段中
            this.putValueToResult(targetResultMap, relationEntity, joinTableResult);
        }
    }


    /**
     * 将目标结果集的数据放入主查询的字段中
     * @param targetResult 目标结果集 key 关联字段值  value 关联对象
     * @param relationEntity 主查询带赋值的级联字段
     * @param joinResult 中间表数据
     */
    private void putValueToResult(Map<Object, Object> targetResult, RelationEntity relationEntity, Map<Object, List<Object>> joinResult) {
        final RelationFieldInfo associationFieldInfo = relationEntity.getRelationFieldInfo();
        final Field field = associationFieldInfo.getField();
        targetResult.forEach((column, val) -> {
            final List<Object> selfList = joinResult.get(column);
            if (Objects.nonNull(selfList)) {
                selfList.forEach(result -> {
                    List<Object> listProperty = ReflectUtil.getValue(field, result);
                    if (Objects.isNull(listProperty)) {
                        listProperty = ReflectUtil.newInstanceList(field.getType());
                        ReflectUtil.setValue(field, result, listProperty);
                    }
                    // 处理关联字段值的类型
                    listProperty.add(this.getValueWithTargetClass(val));
                });
            }
        });
    }


    /**
     * 按照指定字段的值将list集合转换为map集合
     * @param resultList 结果集数据
     * @param fieldInfo 指定字段的FieldInfo
     * @return map
     */
    private Map<Object, Object> listToMap(List<?> resultList, FieldInfo fieldInfo) {
        Map<Object, Object> selfResultMap = new HashMap<>(resultList.size());
        Field field = fieldInfo.getField();
        resultList.forEach(result -> {
            Object resultKey = ReflectUtil.getValue(field, result);
            if (Objects.nonNull(resultKey)) { // 主查询对象的指定字段值只有不为null的情况才进行处理
                selfResultMap.put(resultKey, result);
            }
        });
        return selfResultMap;
    }


    /***
     * 构建目标表的查询sql
     * @param targetBeanInfo 目标表实例的BeanInfo
     * @param nestedQueryParam 内嵌参数集合
     * @param columnValueSet  字段值集合
     * @return sqlStatement
     */
    private SqlStatement buildTargetQuerySql(BeanInfo targetBeanInfo, Map<String, Object> nestedQueryParam, Set<?> columnValueSet) {
        QueryWrapper queryWrapper = this.createQueryWrapperByColumnValues(columnValueSet, this.getTargetColumn(targetBeanInfo), this.getTargetTable(targetBeanInfo));
        queryWrapper.select(this.getSelectColumnsWithTargetClass(targetBeanInfo));
        // 判断配置了外部条件
        this.applyConditionAndOrderBy(queryWrapper, manyToMany.condition(), manyToMany.orderBy(), nestedQueryParam);
        // 返回构建后的sql及其参数
        return new SelectBuilder().buildSelect(queryWrapper, targetBeanInfo);
    }


    /**
     * 查询中间表数据放入map集合中
     * @param statement statement
     * @return map
     */
    private Map<Object, List<Object>> queryJoinTable(Statement statement, List<?> resultList) {
        BeanInfo selfBeanInfo = statement.getBeanInfo();
        FieldInfo selfColumn = this.getSelfColumn(selfBeanInfo);
        // 将主查询的对象按照指定字段值放入Map
        Map<Object, Object> selfResultMap = this.listToMap(resultList, selfColumn);
        // 构建sql
        QueryWrapper queryWrapper = this.createQueryWrapperByColumnValues(selfResultMap.keySet(), manyToMany.joinSelfColumn(), manyToMany.joinTable());
        SqlStatement sqlStatement = new SelectBuilder().buildSelect(queryWrapper, null);
        // 构建statement
        Statement joinStatement = getJoinTableStatement(statement, sqlStatement);
        // 执行sql
        List<Map<String, Object>> joinQueryList = statement.getExecutor().queryList(joinStatement);
        // 更改数据结构为Map
        Map<Object, List<Object>> joinResultMap = new HashMap<>();
        joinQueryList.forEach(map -> {
            final Object groupKey = map.get(manyToMany.joinTargetColumn());
            List<Object> resultGroup = joinResultMap.get(groupKey);
            if (Objects.isNull(resultGroup)) {
                resultGroup = new ArrayList<>();
                joinResultMap.put(groupKey, resultGroup);
            }
            final Object selfResult = selfResultMap.get(map.get(manyToMany.joinSelfColumn()));
            resultGroup.add(selfResult);
        });

        return joinResultMap;
    }

    /**
     * 返回查询中间表的sql及其上下文
     * @param statement 主查询的sql上下文
     * @param sqlStatement 中间表sql及其参数
     * @return Statement
     */
    private static Statement getJoinTableStatement(Statement statement, SqlStatement sqlStatement) {
        Statement joinStatement = new Statement();
        joinStatement.setModule(Map.class);
        joinStatement.setNamespace(statement.getNamespace());
        joinStatement.setSqlCommandType(SqlCommandType.SELECT);
        joinStatement.setLogger(statement.getLogger());
        joinStatement.setKeyGenerator(new NoneKeyGenerator());
        joinStatement.setBoundSql(new BoundSql(sqlStatement.getSql(), sqlStatement.getParamList()));
        joinStatement.setConfiguration(statement.getConfiguration());
        return joinStatement;
    }


    protected FieldInfo getSelfColumn(BeanInfo beanInfo) {
        Map<String, FieldInfo> columnInfoMap = beanInfo.getFieldMap();
        String selfColumn = this.manyToMany.selfField();
        if (StringUtil.isNoNull(selfColumn)) {
            if (!columnInfoMap.containsKey(selfColumn)) {
                throw new EntityException(String.format("字段%s在类%s中不存在", selfColumn, beanInfo.getModule().getName()));
            }
            return columnInfoMap.get(selfColumn);
        } else {
            FieldInfo idFieldInfo = beanInfo.getIdColumnInfo();
            if (Objects.isNull(idFieldInfo)) {
                throw new EntityException(String.format("类%s缺少id字段,当@RelationManyToMany注解的selftColumn缺省时会将@Id标注的字段名当做默认名", beanInfo.getModule().getName()));
            }
            return idFieldInfo;
        }
    }

    @Override
    public Class<?> getTargetClass() {
        return manyToMany.targetClass();
    }


    /**
     * 返回目标实体的指定FieldInfo
     * @param targetBeanInfo 目标targetBeanInfo
     * @return FieldInfo
     */
    private FieldInfo getTargetFieldInfo(BeanInfo targetBeanInfo) {
        Map<String, FieldInfo> columnInfoMap = targetBeanInfo.getFieldMap();
        String targetColumn = this.manyToMany.targetField();
        if (StringUtil.isNoNull(targetColumn)) {
            if (!columnInfoMap.containsKey(targetColumn)) {
                throw new EntityException(String.format("字段%s在类%s中不存在", targetColumn, targetBeanInfo.getModule().getName()));
            }
            return columnInfoMap.get(targetColumn);
        } else {
            FieldInfo idFieldInfo = targetBeanInfo.getIdColumnInfo();
            if (Objects.isNull(idFieldInfo)) {
                throw new EntityException(String.format("类%s缺少id字段,当@RelationManyToMany注解的targetColumn缺省时会将被@Id标注的字段名当做默认名", targetBeanInfo.getModule().getName()));
            }
            return idFieldInfo;
        }
    }


    protected String getTargetColumn(BeanInfo beanInfo) {
        return this.getTargetFieldInfo(beanInfo).getColumn();
    }


    protected String[] getSelectColumns() {
        return this.manyToMany.selectColumns();
    }
}
